package com.physics.mps.module.abaqus;

import cn.hutool.core.collection.CollectionUtil;
import com.physics.mps.common.exception.BusinessException;
import com.physics.mps.entity.vo.*;
import com.physics.mps.module.base.AbstractBaseComputeHandler;
import com.physics.mps.module.util.PyUtils;
import com.physics.mps.entity.vo.*;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.physics.mps.entity.enums.CodeEnum.ABAQUS_COMPUTE_PARAM_NOT_SET;


/**
 * @author mcsoft
 * @see AbaqusComputeService
 * @since 2023/9/6
 */
@Slf4j
public abstract class AbstractAbaqusComputeHandler extends AbstractBaseComputeHandler {

    // ~ Variable & Constant
    // ========================================================================================================

    /**
     * 全局计算参数
     */
    protected AbaqusComputeParam abaqusComputeParam;

    /**
     * 部件参数列表
     */
    protected List<AbaqusPartVo> parts;

    /**
     * 部件名集合
     */
    protected Set<String> partNames;

    /**
     * 网格参数
     */
    protected AbaqusMeshVo meshVo;

    /**
     * 作业名称
     */
    protected AbaqusJobVo jobVo;

    /**
     * 绑定
     */
    protected List<AbaqusTieVo> tieVos;

    /**
     * 是否设置全局参数
     */
    protected boolean setComputeParam = false;

    /**
     * 电路板部件名
     */
    protected String board;

    // ~ Public method
    // ========================================================================================================

    /**
     * 创建命令
     *
     * @return 命令值
     */
    @Override
    public String createCommand() throws Exception {

        if (!this.setComputeParam) {
            throw new BusinessException(ABAQUS_COMPUTE_PARAM_NOT_SET);
        }

        StringBuilder builder = new StringBuilder();

        // 1.导入依赖
        builder.append(this.createImportCommand());

        // 2.创建并保存cae文件，设置工作目录
        builder.append(this.createSetWorkDirCommand());

        // 3.导入stp文件
        builder.append(this.createImportStpCommand());

        // 4.创建部件和材料，并把材料属性附到部件上
        builder.append(this.createPartCommand());

        //4-2.部件绑定
        builder.append(this.createTieCommand());

        //5-7.创建分析步并设置分析步、场量、边界条件等
        builder.append(this.createStepAndField());

        // 8.网格
        builder.append(this.createPartNameCommand());

        // 9.任务
        builder.append(this.createJobCommand());

        //10.导出结果 传热(最大最小值) 振动(相对位移)
        builder.append(this.createExportCommand());

        return builder.toString();
    }

    /**
     * 设置全局参数
     * @param abaqusComputeParam 通过前端传入并解析的参数
     * @return 脚本拼接的处理器
     */
    public AbstractAbaqusComputeHandler setAbaqusComputeParam(AbaqusComputeParam abaqusComputeParam) {
        this.abaqusComputeParam = abaqusComputeParam;

        this.parts = this.abaqusComputeParam.getParts();

        this.partNames = this.abaqusComputeParam.getPartNames();

        this.tieVos = this.abaqusComputeParam.getTieVos();

        this.meshVo = this.abaqusComputeParam.getMeshVo();

        this.jobVo = this.abaqusComputeParam.getJobVo();

        this.setComputeParam = true;

        return this;
    }

    // ~ Override method
    // ========================================================================================================


    /**
     * 开始计算
     * @return 计算结果
     */
    @Override
    public String startCompute() throws Exception {
        String command = this.createCommand();
        return null;
    }

    /**
     * 创建设置部件命令
     * @param builder
     */
    @Override
    public void createPartCommandImpl(StringBuilder builder) {
        if (CollectionUtil.isEmpty(parts)) {
            return;
        }


        int materialNum = 1;
        for (AbaqusPartVo part : parts) {
            String partName = part.getName();
            String partNameReplace = partName.replace("-", "");
            List<float[]> coordinate = part.getCoordinate();
            Map<String, List<List<String>>> materials = part.getMaterials();

            // 4.1 选取部件
            builder.append(partNameReplace)
                    .append(" = ")
                    .append(this.createMdbModelsCommand("Model-1"))
                    .append(".parts['")
                    .append(partName)
                    .append("']")
                    .append(PyUtils.WRAP);

            // 4.2 创建材料
            String materialName = "Material-" + materialNum;
            builder
                    .append(this.createMdbModelsCommand("Model-1"))
                    .append(".Material(name='")
                    .append(materialName)
                    .append("')")
                    .append(PyUtils.WRAP);
            materials.forEach((name, value) -> {
                builder.append(this.createMdbModelsCommand("Model-1"))
                        .append(".materials['")
                        .append(materialName).append("'].")
                        .append(name);
                if (value.size() == 1 && value.get(0).size() == 1) {
                    builder.append("(table=((")
                            .append(value.get(0).get(0))
                            .append(", ), ))")
                            .append(PyUtils.WRAP);
                } else {
                    builder.append("(table=(")
                            .append(PyUtils.materialConvertToString(value))
                            .append("))")
                            .append(PyUtils.WRAP);
                }
            });

            // 4.3创建截面
            String sectionName = "Section-" + materialNum;
            builder
                    .append(this.createMdbModelsCommand("Model-1"))
                    .append(".HomogeneousSolidSection(name='")
                    .append(sectionName)
                    .append("', material='")
                    .append(materialName)
                    .append("', thickness=None)")
                    .append(PyUtils.WRAP);

            // 4.4选择部件cells和根据坐标选cells
            String cellName = "cell" + materialNum;
            builder.append(cellName).append(" = ").append(partNameReplace).append(".cells").append(PyUtils.WRAP);
            String regionName = "region" + materialNum;
            String setName = "Set-" + materialNum;
            builder.append(regionName).append("=").append(partNameReplace).append(".Set(").append("cells=").append(cellName).append(", name='").append(setName).append("')").append(PyUtils.WRAP);

            // 4.5 赋予材料属性
            builder.append(partNameReplace).append(".SectionAssignment(region=").append(regionName).append(", sectionName='").append(sectionName).append("', offset=0.0, offsetType=MIDDLE_SURFACE, offsetField='', thicknessAssignment=FROM_SECTION)").append(PyUtils.WRAP);

            // 4.6 部件生成实例装配到a上
            builder.append("a = mdb.models['Model-1'].rootAssembly").append(PyUtils.WRAP)
                    .append("a.DatumCsysByDefault(CARTESIAN)").append(PyUtils.WRAP);
            builder.append("a.Instance(name='").append(partName).append("', part=").append(partNameReplace).append(", dependent=ON)").append(PyUtils.WRAP);

            materialNum++;
        }
    }


    // ~ Private method
    // ========================================================================================================

    /**
     * 创建设置工作目录命令
     *
     * @return 设置工作目录命令
     */

    /**
     * 创建选中 model 命令
     *
     * @param modelName model 名称
     * @return 选中 model 命令
     */
    public String createMdbModelsCommand(String modelName) {
        return "mdb.models['" + modelName + "']";
    }

    /**
     * 根据名称创建 Job 命令
     *
     * @param jobName job名称
     * @return 创建 Job 命令
     */
    private String createJobCommandByName(String jobName) {
        return "mdb.jobs['" + jobName + "']";
    }


    // ~ Autowired
    // ========================================================================================================

    // ~ Get & Set
    // ========================================================================================================

    /**
     * 创建导入依赖命令
     * @param builder
     */
    @Override
    public void createImportCommandImpl(StringBuilder builder) {
        builder.append("# -*- coding: mbcs -*-").append(PyUtils.WRAP)
                .append(PyUtils.createImportCommand("abaqus", "*"))
                .append(PyUtils.createImportCommand("abaqusConstants", "*"))
                .append(PyUtils.createImportCommand("math", "*"))
                .append(PyUtils.createImportCommand("driverUtils", "executeOnCaeStartup"))
                .append(PyUtils.createImportCommand("", "os"))
                .append(PyUtils.createImportCommand("caeModules", "*"))
        ;
    }

    /**
     * 设置工作目录
     * @param builder
     */
    @Override
    public void createSetWorkDirCommandImpl(StringBuilder builder) {
        builder.append("Mdb()").append(PyUtils.WRAP);
        builder.append("mdb.saveAs(pathName='")
                .append(this.caePath).append("')")
                .append(PyUtils.WRAP)
                .append(PyUtils.createChdirCommand(this.workDir))
        ;
    }

    /**
     * 创建导入 STP 文件路径
     * @param builder
     */
    @Override
    public void createImportStpCommandImpl(StringBuilder builder) {
        //导入stp文件
        builder.append("step = mdb.openStep('")
                .append(this.stpPath)
                .append("', scaleFromFile=ON)")
                .append(PyUtils.WRAP)
        ;

        //按顺序分别导入stp文件中的路径
        builder.append("mdb.models['Model-1'].PartFromGeometryFile(name='COMPOUND', geometryFile=step, combine=False, dimensionality=THREE_D, type=DEFORMABLE_BODY)").append(PyUtils.WRAP);
        int partNum = partNames.size();
        for (int i = 0; i < partNum - 1; i++) {
            builder.append(this.createMdbModelsCommand("Model-1"))
                    .append(".PartFromGeometryFile(name='COMPOUND-")
                    .append(i + 1)
                    .append("', geometryFile=step")
                    .append(", bodyNum=").append(i + 2)
                    .append(", combine=False, dimensionality=THREE_D, type=DEFORMABLE_BODY)")
                    .append(PyUtils.WRAP)
            ;
        }
    }


    /**
     * 创建设置部件名、生成网格命令
     * @param builder
     */
    @Override
    public void createPartNameCommandImpl(StringBuilder builder) {
        if (CollectionUtil.isEmpty(this.partNames)) {
            return;
        }

        String size = meshVo.getSize();
        String deviationFactor = meshVo.getDeviationFactor();
        String minSizeFactor = meshVo.getMinSizeFactor();
//        builder.append("elemType1 = mesh.ElemType(elemCode=DC3D8, elemLibrary=STANDARD)").append(PyUtils.WRAP);
//        builder.append("elemType2 = mesh.ElemType(elemCode=DC3D6, elemLibrary=STANDARD)").append(PyUtils.WRAP);
//        builder.append("elemType3 = mesh.ElemType(elemCode=DC3D4, elemLibrary=STANDARD)").append(PyUtils.WRAP);
        builder.append(this.createElemTypeCommand());
        for (String partName : partNames) {
            builder.append("p = ")
                    .append(this.createMdbModelsCommand("Model-1"))
                    .append(".parts['").append(partName).append("']").append(PyUtils.WRAP);
            builder.append("c = p.cells").append(PyUtils.WRAP);
            // 掩码规律，体的数量为n，则选择所有体的掩码为2^n-1,转换为十六进制输入
            builder.append("i = 2 ** len(c) - 1").append(PyUtils.WRAP);
            builder.append("hexstr = \"%x\" % i").append(PyUtils.WRAP);
            builder.append("str1 = '[#' + hexstr + ' ]'").append(PyUtils.WRAP);
            builder.append("cells = c.getSequenceFromMask(mask=(str1, ), )").append(PyUtils.WRAP);
            builder.append("pickedRegions =(cells, )").append(PyUtils.WRAP);
            //生成网格
            builder.append("p.setMeshControls(regions=cells, elemShape=TET, technique=FREE)").append(PyUtils.WRAP);
            builder.append("p.seedPart(size=").append(size).append(", deviationFactor=").append(deviationFactor).append(", minSizeFactor=").append(minSizeFactor).append(")").append(PyUtils.WRAP);
            builder.append("p.generateMesh()").append(PyUtils.WRAP);
            builder.append("p.setElementType(regions=pickedRegions, elemTypes=(elemType1, elemType2, elemType3))").append(PyUtils.WRAP);
        }
        builder.append("a1 = mdb.models['Model-1'].rootAssembly").append(PyUtils.WRAP);
        builder.append("a1.regenerate()").append(PyUtils.WRAP);
    }


    /**
     * 创建设置任务命令
     * @param builder
     */
    @Override
    public void createJobCommandImpl(StringBuilder builder) {
        //9.创建作业
        //9.1 设置cpu gpu 时域
        builder.append("mdb.Job(name='").append(jobVo.getJobName()).append("', model='Model-1', description='', type=ANALYSIS,")
                .append(PyUtils.WRAP)
                .append("    atTime=None, waitMinutes=0, waitHours=0, queue=None, memory=90,")
                .append(PyUtils.WRAP)
                .append("    memoryUnits=PERCENTAGE, getMemoryFromAnalysis=True,").append(PyUtils.WRAP)
                .append("    explicitPrecision=SINGLE, nodalOutputPrecision=SINGLE, echoPrint=OFF,").append(PyUtils.WRAP)
                .append("    modelPrint=OFF, contactPrint=OFF, historyPrint=OFF, userSubroutine='',").append(PyUtils.WRAP)
                .append("    scratch='', resultsFormat=ODB, multiprocessingMode=DEFAULT, numCpus=")
                .append(jobVo.getNumCpus()).append(",").append(PyUtils.WRAP)
                .append("    numDomains=")
                .append(jobVo.getNumDomain())
                .append(", numGPUs=")
                .append(jobVo.getNumGpus()).
                append(")").append(PyUtils.WRAP);
        builder.append("mdb.save()").append(PyUtils.WRAP);
        builder
                .append(this.createJobCommandByName(jobVo.getJobName()))
                .append(".submit(consistencyChecking=OFF)").append(PyUtils.WRAP);
        builder.append(this.createJobCommandByName(jobVo.getJobName()))
                .append(".waitForCompletion()").append(PyUtils.WRAP);
        //9.2 按照不同模块显示结果场并导出图片
        if (jobVo.getVariableLabel() != null && jobVo.getOutputPosition() != null) {
            builder.append("o1 = session.openOdb(name='").append(this.workDir).append("\\\\").append(jobVo.getJobName()).append(".odb')").append(PyUtils.WRAP);
            builder.append("session.viewports['Viewport: 1'].setValues(displayedObject=o1)").append(PyUtils.WRAP);
            builder.append("session.viewports['Viewport: 1'].odbDisplay.display.setValues(plotState=(CONTOURS_ON_DEF, ))").append(PyUtils.WRAP);
            builder.append("session.viewports['Viewport: 1'].odbDisplay.setPrimaryVariable(variableLabel='").append(jobVo.getVariableLabel())
                    .append("', outputPosition=").append(jobVo.getOutputPosition());
            if (jobVo.getRefinement() != null) {
                builder.append(", refinement=").append(jobVo.getRefinement());
            }
            builder.append(", )").append(PyUtils.WRAP);
            String pngName = workDir + "\\\\PNG\\\\" + jobVo.getJobName();
            builder.append("session.printToFile(fileName='")
                    .append(pngName)
                    .append("', format=PNG, canvasObjects=(session.viewports['Viewport: 1'], ))")
                    .append(PyUtils.WRAP);
        }
    }

    /**
     * 创建设置绑定命令
     * @param builder
     */
    @Override
    public void createTieCommandImpl(StringBuilder builder) {
        for (AbaqusTieVo tie : tieVos) {
            builder.append("s1 = a.instances['")
                    .append(tie.getFaces1Instances())
                    .append("'].faces")
                    .append(PyUtils.WRAP);
            builder.append("side1Faces1 = s1")
                    .append(".findAt(")
                    .append(PyUtils.coordinateConvertToString(tie.getFace1Coordinate())).append(")")
                    .append(PyUtils.WRAP);
            builder.append("region1=regionToolset.Region(side1Faces=side1Faces1)").append(PyUtils.WRAP);

            builder.append("s2 = a.instances['")
                    .append(tie.getFaces2Instances())
                    .append("'].faces")
                    .append(PyUtils.WRAP);
            builder.append("side1Faces2 = s2")
                    .append(".findAt(")
                    .append(PyUtils.coordinateConvertToString(tie.getFace2Coordinate())).append(")")
                    .append(PyUtils.WRAP);
            builder.append("region2=regionToolset.Region(side1Faces=side1Faces2)").append(PyUtils.WRAP);

            builder.append(this.createMdbModelsCommand("Model-1"))
                    .append(".Tie(name='")
                    .append(tie.getFaces1()).append("-").append(tie.getFaces2())
                    .append("', master=region1")
                    .append(", slave=region2")
                    .append(", positionToleranceMethod=SPECIFIED")
                    .append(", adjust=OFF")
                    .append(", tieRotations=ON")
                    .append(", thickness=ON)")
                    .append(PyUtils.WRAP);
        }
    }
}
